package es.uned.etsii.si3.taller;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import com.j256.ormlite.dao.CloseableWrappedIterable;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.jdbc.JdbcConnectionSource;
import com.j256.ormlite.stmt.QueryBuilder;
import com.j256.ormlite.table.TableUtils;

/**
 * 
 * Esta clase contiene los controles de los objetos DAO
 *
 */
public class FactoriaDAO {
	private static final String databaseUrl = "jdbc:hsqldb:mem:.";
	public static Map<Class<?>, Dao<?,?>> daoTable;

	/**
	 * Devuelve la conexión a la base de datos
	 * @throws SQLException
	 */
	public
	static JdbcConnectionSource getConnectionSource() throws SQLException {
		return new JdbcConnectionSource(databaseUrl);
	}
	
	private
	static List<Class<?>> buscaClasesDerivadas(String paquete, Class<?> ancestro) throws IOException, ClassNotFoundException {
		ArrayList<Class<?>> classes = new ArrayList<Class<?>>();
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		String path = paquete.replace('.', '/');
		
		Enumeration<URL> resources = classLoader.getResources(path);
		if (resources.hasMoreElements()) {
			for (File f: (new File(resources.nextElement().getFile())).listFiles()) {
				if ((!f.isDirectory()) && (f.getName().endsWith(".class"))) {
					Class<?> c = Class.forName(paquete + "." + f.getName().substring(0, f.getName().length() - 6));
					Class<?> sC = c.getSuperclass();
					
					// Creo que esto pasa si la clase no extiende otra de forma explícita
					// Da igual, no es de las que queremos registrar...
					if (sC == null)
						continue;
		
					if (sC.equals(ancestro)) {
						classes.add(c);
					}
				}
			}
		}

		return classes;
	}


	/**
	 * Inicializa la interfaz DAO
	 * @throws SQLException
	 */
	public
	static void inicializarInterfazDAO() throws SQLException {
			try {
				if (daoTable == null) {
					daoTable = new Hashtable<Class<?>, Dao<?,?>>();

					for (Class<?> c: buscaClasesDerivadas("es.uned.etsii.si3.taller", Persistente.class)) {
						inicializaTipo(c);
					}
				}

			}
			catch (Exception e) {
				e.printStackTrace();
			}
    }

	private
	static
	<T> void inicializaTipo(Class<T> c) throws SQLException {
		JdbcConnectionSource connection = getConnectionSource();

		TableUtils.createTable(connection, c);
		daoTable.put(c, DaoManager.createDao(connection, c));
	}

	/**
	 * Devuelve un DAO para una clase determinada
	 * 
	 * @param c clase de la que obtener el DAO
	 * @return	objeto DAO
	 * @throws SQLException
	 */
	public
	static <T> Dao<T, Integer> getDao(Class<T> c) throws SQLException {
		@SuppressWarnings(value="unchecked")
		Dao<T, Integer> dao = (Dao<T, Integer>)daoTable.get(c);
		return dao;
	}
	
	/**
	 * Permite obtener un querybuilder para una clase dada
	 * 
	 * @param c	clase de la que obtener el query builder
	 * @return el querybuilder buscado
	 * @throws SQLException
	 */
	public
	static <T> QueryBuilder<T, Integer> getQueryBuilder(Class<T> c) throws SQLException {
		return getDao(c).queryBuilder();
	}

	/**
	 * Recupera un objeto de la base de datos dado su ID
	 *
	 * @param  id el identificado del objeto
	 * @return    el objeto deseado si existe; null en caso contrario
	 */
	public
	static <T> T recuperar(Class<T> c, int id) throws SQLException {
		return getDao(c).queryForId(id);
	}
	
	public
	static <T> CloseableWrappedIterable<T> getIteradorContenido(Class<T> c) throws SQLException {
		return getDao(c).getWrappedIterable();
	}
}
